feat(cli): send pasted images to the /btw side agent - #3619
Conversation
The btw child agent previously vetoed every tool call. Allow the read-only tools Read, Grep, and Glob so side questions about the codebase can be answered from current file contents; write and execute tools stay disabled.
The btw panel's input path was text-only: pasted image placeholders went to the side agent as literal text and the bytes never left the image store. Expand media placeholders into daemon file-ref parts exactly like the main send path (ingestion wait, extraction, capability validation, staged-media lease with an exact-binding submission id), allow the read-only ReadMediaFile tool in the btw child, and mention it in the side-question reminder. Stacked on #3613 (feat/btw-readonly-tools), which introduced the btw read-only tool allowlist this change extends.
🦋 Changeset detectedLatest commit: 8e51319 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8e513195a1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| this.host.trackBtwDispatch( | ||
| prepared.lease, | ||
| this.withInteractiveAgent(agentId, send), |
There was a problem hiding this comment.
Route side-agent turn events through the lease tracker
When an actual /btw turn starts or ends, its agentId is the side-agent ID, so SessionEventHandler.handleEvent returns through routeChildAgentEvent before invoking the host's staging handleTurnStarted or handleTurnEnded hooks. Consequently, the lease tracked here is never bound or released after successful side prompts, and each pasted image/video remains retained in daemon staging until the session is closed or its TTL expires. The new test misses this because emitTurn emits both events for agentId: 'main'; the side-agent routing path must notify the staging tracker as well.
Useful? React with 👍 / 👎.
| const useSkills = inlineSkillActivations !== undefined && inlineSkillActivations.length > 0; | ||
| // Skill bundles have no prompt-id channel, so they match the main turn's | ||
| // inline-skill path: media rides along without a staged lease. | ||
| const prepared = await this.host.prepareBtwPrompt(prompt, { stage: !useSkills }); |
There was a problem hiding this comment.
Abort preparation when the BTW panel is closed
If a just-pasted image or video is still being ingested, this await can last up to two seconds. During that interval, Esc or Ctrl-C closes/unregisters the panel and calls session.cancel() while the child is still idle, but the continuation never checks whether the panel remains active and subsequently dispatches the prompt anyway. This produces an invisible side-agent request after the user explicitly canceled it (and can overlap a newly opened /btw panel); cancellation or panel identity should be rechecked before dispatch, with any prepared lease released on abandonment.
Useful? React with 👍 / 👎.
|
Closing in favor of the desktop implementation in kimi-code-app. Note: the daemon-side ReadMediaFile allowlist for /btw is intentionally not carried over for now — the desktop media path is session-level and does not need it; can be re-added separately if a model without image input needs it. |
|
Closing in favor of the desktop implementation in kimi-code-app. Note: the daemon-side ReadMediaFile allowlist for /btw is intentionally not carried over — the desktop media path is session-level and does not need it; can be re-added separately if a model without image input needs it. |
Problem
The /btw side-question panel is a text-only channel: when the user pastes an image and asks a side question, the
[image #N]placeholder goes to the side agent as literal text and the image bytes never leave the TUI's attachment store — the side agent cannot see the image at all. The daemon side is already ready (mediaResolver / prompt media intake / session media store all apply to the forked btw child); only the TUI input path drops the media.What changed
extractMediaAttachments, model capability validation, and a staged-media lease with an exact-binding submission id (promptId) so the staging upload is released when the consuming turn ends. Skill bundles match the main inline-skill path (media rides along, no lease). This covers both the initial/btw <question>prompt and follow-up panel input.ReadMediaFile(already auto-approved by the default-tool-approve policy), so models without image input can still view images via the degraded<image path>form; the side-question reminder mentions it.Relationship to #3613
Stacked on #3613 (
feat/btw-readonly-tools, base of this PR): that PR introduced theBTW_READONLY_TOOLSallowlist this change extends withReadMediaFile. Once #3613 merges, this PR's base retargets to main. Self-contained alternative (duplicating the allowlist on main) was rejected to avoid a guaranteed merge conflict between the two PRs.Tests
btw.test.ts(agent-core-v2): the read-only allow test now pins the exact allowlist{Read, Grep, Glob, ReadMediaFile}and verifies ReadMediaFile passes the veto.kimi-tui-message-flow.test.ts: two new cases — a pasted image in the initial/btwprompt and in follow-up panel input is sent askimi-file://parts with apromptId, and the staging upload survives until the consuming turn ends.